package voitures;

import java.io.File;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import reso.Launcher;
import portes.Porte;

public class ServeurVoituresImpl extends UnicastRemoteObject implements ServeurVoitures{
	
	private ArrayList<Porte> portes;
	private final ScheduledThreadPoolExecutor scheduler;
	// Date maximale de la dernière demande d'entrée ou sortie
	private final static int MAX_DATE_IN_SECONDS = 10;
	// proportion de demandes d'entrées dans l'ensemble des demandes envoyées
	// (sous réserve qu'une sortie soit possible)
	private final static double ENTRY_RATE_AMONG_DEMANDS = .8;
	// compte du nombre de voitures à l'intérieur du parking (si les algorithmes fonctionnent bien !)
	private Integer nbvoituresentrees = 0;
	// nombre de demandes qui seront générées
	private static int nbdemandes = 6;
	// Variables utilisées pour la génération des délais aléatoires entre les envois de demandes.
	private static Random random = new Random(System.currentTimeMillis());
	
	public ServeurVoituresImpl() throws RemoteException {
		super();
		this.portes = new ArrayList<Porte>();
		this.scheduler = new ScheduledThreadPoolExecutor(20);
	}

	public static void main(String[] args) throws RemoteException, InterruptedException {
		Launcher.setServerHostname();
		ServeurVoitures serveur = new ServeurVoituresImpl();
		Registry registry = LocateRegistry.getRegistry(1099);
		registry.rebind(ServeurVoitures.NAME, serveur);
		System.out.println("Lancement du serveur de voiture");
					
		// Toutes les voitures doivent se déclarer au préalable
		((ServeurVoituresImpl) serveur).attenteDeclaration();
		System.out.println("Toutes les portes sont bien déclarées au serveur de voitures");
				
		int delay;
		
		for(int i = 0 ; i < nbdemandes ; i++){
			delay = (int) (random.nextDouble() * MAX_DATE_IN_SECONDS * 1000);
			serveur.envoiDemande(delay, (int) (random.nextDouble() * Porte.nombreportes));
		}
	}

	/**
	 * Attendre que l'ensemble des portes soient déclarées (via la méthode ajouterPorte()).
	 * @throws RemoteException 
	 * 
	 */
	private void attenteDeclaration() throws InterruptedException, RemoteException {
		int compte = 0;
		while(portes.size() < Porte.nombreportes){
			synchronized(this){
				this.wait();
			}
			System.out.println("Déclaration de la porte "+ compte++);
		}
		// Une fois toutes les portes déclarées, on peut démarrer
		for(int i = 0 ; i < portes.size() ; i++){
			portes.get(i).init();
		}
	}

	@Override
	/**
	 * Recevoir la déclaration d'une Porte.
	 * 
	 * @param porte référence de la Porte qui se déclare.
	 */
	public synchronized void ajouterPorte(Porte porte) {
		portes.add(porte);
		synchronized(this){
			this.notify();
		}
	}

	@Override
	/**
	 * Envoyer une demande (d'entrée ou de sortie) à une Porte.
	 * 
	 * @param delay délai d'attente avant l'envoi effectif de la demande.
	 * @param numeroPorte id de la Porte à laquelle la demande est adressée.
	 * @param entree true s'il faut envoyer une demande d'entrée, false pour une demande de sortie
	 */
	public void envoiDemande(final int delay, final int numeroPorte) {	
			scheduler.schedule(new Runnable() {
				@Override
				public void run() {
					boolean entree;
						/*
						 * Le booleen entree vaut true si on fait une demande d'entrée et 
						 * false pour une demande de sortie
						 * 
						 * Si le parking est vide, on doit faire une demande d'entrée.
						 * Sinon on choisit au hasard.
						 * 
						 * le boolean random.nextDouble() < ENTRY_RATE_AMONG_DEMANDS est vrai avec une probabilité
						 * de ENTRY_RATE_AMONG_DEMANDS, qui correspondra donc à la proportion de demandes d'entrée
						 * parmi l'ensemble des demandes (sauf celles lorsque le parking est vide)
						 */
						entree = (nbvoituresentrees == 0) || (random.nextDouble() < ENTRY_RATE_AMONG_DEMANDS);
						
						Porte porte = portes.get(numeroPorte);
						// Cas d'une demande d'entrée
						if(entree){
							System.out.println("[SERVEUR] Demande d'entrée à la porte " + numeroPorte + " après un délai de " + delay + " ms");
							try {
								porte.demandeEntree();
							} 
							catch (Exception e) {e.printStackTrace();} 
						}
						// Cas d'une demande de sortie
						else{
							// demande de sortie possible uniquement s'il y a au moins une voiture dans le parking
							if(nbvoituresentrees > 0){
								System.out.println("[SERVEUR] Demande de sortie à la porte " + numeroPorte + " après un délai de " + delay + " ms");
								// le nombre de voitures dans le parking est decrémenté
								try {
									porte.demandeSortie();
								}
								catch (Exception e) {e.printStackTrace();} 
							}
						}
						
						// fin de ce Thread, la demande a été traitée par la porte
						synchronized(nbvoituresentrees){
							if(entree){
								// si nbvoituresentrees < Porte.nombrePlacesinitial, la demande aura été acceptée,
								nbvoituresentrees = Math.min(nbvoituresentrees + 1, Porte.nombreplacesinitial);
							}
							else{
								nbvoituresentrees = Math.max(nbvoituresentrees - 1, 0);
							}
						}
				}
			}, delay, TimeUnit.MILLISECONDS);
	}
}
